package repository

import (
	"happy-sudoku/internal/codec"
	"happy-sudoku/internal/model"
	"happy-sudoku/internal/util"

	"github.com/go-redis/redis"
	"github.com/pkg/errors"
)

// IPlayerStateRepo is to be implemented by
// PlayerStateRepo
type IPlayerStateRepo interface {
	GetOne(playerID int) (*model.PlayerState, error)
	GetList(playerIDs []int) (map[int]*model.PlayerState, error)
	Update(playerID int, state model.PlayerState) error
	Delete(playerIDs ...int) error
	Create(playerID int, state model.PlayerState) error
}

const (
	playerStateErrHeader = "PlayerStateRepo"
)

const (
	keyPlayerState = "player:state"
)

func hFieldID(id int) string {
	return util.IntToString(id)
}

// PlayerStateRepo implements IPlayerStatRepo
// Use redis as data layer
// Use Hash "player:state"
// Fields: playerId1, playerId2, playerId3...
type PlayerStateRepo struct {
	rdb *redis.Client
}

// NewPlayerStateRepo new playerState repo
func NewPlayerStateRepo(rdb *redis.Client) *PlayerStateRepo {
	return &PlayerStateRepo{
		rdb: rdb,
	}
}

// GetOne returns one state
func (r *PlayerStateRepo) GetOne(playerID int) (*model.PlayerState, error) {
	const errHeader = playerStateErrHeader + ".GetOne"

	data, err := r.rdb.HGet(keyPlayerState, hFieldID(playerID)).Result()
	if err != nil {
		if err == redis.Nil {
			// Not Found Error
			return nil, codec.ErrPlayerState404.WithField("playerID", playerID)
		}
		return nil, errors.Wrap(err, errHeader)
	}
	// unmarshal
	state := &model.PlayerState{}
	err = state.Unmarshal(data)
	if err != nil {
		return nil, errors.Wrap(err, errHeader)
	}
	return state, nil
}

// GetList return a map of states (playerID => state)
func (r *PlayerStateRepo) GetList(playerIDs []int) (map[int]*model.PlayerState, error) {
	result := make(map[int]*model.PlayerState)
	for _, id := range playerIDs {
		state, err := r.GetOne(id)
		if err != nil {
			// ignore
			continue
		}
		result[id] = state
	}
	return result, nil
}

// Create creates playerState in playerID
func (r *PlayerStateRepo) Create(playerID int, state model.PlayerState) error {
	const errHeader = playerStateErrHeader + ".Create"

	state.PlayerID = playerID
	data, err := state.Marshal()
	if err != nil {
		return errors.Wrap(err, errHeader)
	}
	err = r.rdb.HSet(keyPlayerState, hFieldID(playerID), data).Err()
	return err
}

// Update updates state
func (r *PlayerStateRepo) Update(playerID int, state model.PlayerState) error {
	const errHeader = playerStateErrHeader + ".Update"

	state.PlayerID = playerID
	data, err := state.Marshal()
	if err != nil {
		return errors.Wrap(err, errHeader)
	}
	err = r.rdb.HSet(keyPlayerState, hFieldID(playerID), data).Err()
	if err != nil {
		return errors.Wrapf(err, errHeader)
	}
	return nil
}

// Delete deletes state
func (r *PlayerStateRepo) Delete(playerIDs ...int) error {
	const errHeader = playerStateErrHeader + ".Delete"

	err := r.rdb.HDel(keyPlayerState, util.IntToStringSlice(playerIDs)...).Err()
	if err != nil {
		return errors.Wrap(err, errHeader)
	}
	return err
}
